/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.swe622.rmi.client.facepicclient;

/**
 *
 * @author jasbh292
 */
import com.swe622.rmi.client.gui.GuiDataObject;
import java.net.*;
import java.io.*;
import java.security.NoSuchAlgorithmException;

import java.util.List;

import java.awt.event.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import shared.BaseEvent;
import shared.FileDownloadManager;

import shared.FileInfo;
import shared.FileTransferListener;
import shared.FileTransferManager;
import shared.FileUploadManager;
import shared.PicRegistry;
import shared.QueryResponse;
import shared.RequestType;
import shared.ServerInfo;
import shared.ServerRequest;
import shared.TransferStatus;

public class Client implements FileTransferListener {

  //  private String fileDownloadedDirPath;
    private PicRegistry picRegistry;
    // Holds the file information that is currenting being uploaded or downloaded
    private ArrayList<GuiDataObject> currentTransfers = new ArrayList<GuiDataObject>();
    private final List<String> errors;
    private final List<FileTransferManager> transferManagerList;
    private final List<ListDataListener> downloadListListeners;
    private final List<ListDataListener> uploadListListeners;
    private final ProgressWatcher progressMonitor;
    private ServerInfo serverInfo;
    private String RegistryLocation;

     private String imageFilePath;

    public String getImagePath(){
//Set the path of image
     return imageFilePath;
  }

  public void setImagePath(String imagePath){
      imageFilePath = imagePath;
  }

    public void saveDirectory(String dirPath) {
        //pass the new directory to the download method
       imageFilePath = dirPath;
    }

    public Registry ConnectToRegistry() {

        Registry registry = null;
        try {
            registry = LocateRegistry.getRegistry(RegistryLocation);
        } catch (RemoteException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Cannot connect to registry");
        }
        return registry;
    }

    public void startProgressMonitor() {
        progressMonitor.start();
    }

    public void unexpectedTransferTerminationEvent(BaseEvent event) {
        //throw new UnsupportedOperationException("Not supported yet.");
    }

    public void resumeThisDownload(FileTransferManager transferToResume, ServerInfo targetServer) {

        Socket socket = null;
        try {
            socket = new Socket(targetServer.getAddress(), targetServer.getPort());
        } catch (IOException ex) {
            errors.add("Could not resume " + transferToResume.getFileInfo().getFileName());
        }
        if (socket != null);
        {
            synchronized (transferManagerList) {
                FileDownloadManager targetManager =
                        (FileDownloadManager)transferManagerList.get(transferManagerList.indexOf(transferToResume));
                FileInfo fileInfo = targetManager.getFileInfo();
                ServerRequest request = new ServerRequest();
                request.setFileInfo(fileInfo);
                request.setOffset(targetManager.getBytesTransferred());
                request.setType(RequestType.CLIENTDOWNLOAD);
                writeRequest(socket, request);
                InputStream instream = null;
                try {
                    instream = socket.getInputStream();
                } catch (IOException ex) {
                   errors.add("Could not resume");
                }
                if (instream!=null)
                {
                    targetManager.setInputStream(instream);
                    new Thread(targetManager).start();
                }
            }
            fireDownloadContentsChangedEvent(new ListDataEvent(this,ListDataEvent.CONTENTS_CHANGED,0,transferManagerList.size()));
        }

    }

    public void transferStatusChanged(FileTransferManager m) {

        synchronized (progressMonitor) {
            progressMonitor.notify();
        }
        if(m instanceof FileDownloadManager)
        {
             if (m.getStatus() == TransferStatus.COMPLETE) {
                writeBytesToFile(((FileDownloadManager) m).getFileData(), ((FileDownloadManager) m).getFileInfo());
        }
        }
       
        // fireDownloadContentsChangedEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, transferManagerList.size() - 1));


    }

    private class ProgressWatcher extends Thread {

        private FileTransferManager getTransferInProgress() {
            FileTransferManager currentTransfer = null;
            synchronized (transferManagerList) {
                Iterator<FileTransferManager> mgrIterator = transferManagerList.iterator();
                while (mgrIterator.hasNext() && currentTransfer == null) {
                    FileTransferManager nextMgr = mgrIterator.next();
                    if (nextMgr.getStatus() == TransferStatus.IN_PROGRESS) {
                        currentTransfer = nextMgr;
                    }
                }
            }
            return currentTransfer;
        }

        @Override
        public void run() {

            boolean keepChecking = true;
            while (keepChecking) {
                FileTransferManager currentTransfer = getTransferInProgress();
                while (currentTransfer != null) {
                    while (currentTransfer.getStatus() == TransferStatus.IN_PROGRESS) {
                        fireDownloadContentsChangedEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, transferManagerList.size() - 1));
                        try {
                            sleep(300);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    fireDownloadContentsChangedEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, transferManagerList.size() - 1));
                    currentTransfer = getTransferInProgress();
                }
                synchronized (this) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
                        keepChecking = false;
                    }
                }

            }





            /*
            while (true) {
            boolean updateProgress = false;
            for (FileTransferManager mgr : transferManagerList) {

            if (mgr.getStatus() == TransferStatus.IN_PROGRESS) {
            updateProgress = true;
            }
            }
            if (updateProgress) {
            fireDownloadContentsChangedEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, transferManagerList.size() - 1));
            try {
            sleep(500);
            } catch (InterruptedException e) {
            }
            } else {
            fireDownloadContentsChangedEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, transferManagerList.size() - 1));
            try {
            synchronized (this) {
            wait();
            }
            } catch (InterruptedException e) {
            }
            }
            }
             */
        }
    }
  

    public Client() throws FileNotFoundException, IOException, ClassNotFoundException {
        downloadListListeners = new ArrayList<ListDataListener>();
        uploadListListeners = new ArrayList<ListDataListener>();
        transferManagerList = new ArrayList<FileTransferManager>();
        progressMonitor = new ProgressWatcher();
        errors = new ArrayList<String>();
        File regfile = new File("registryLocation.txt");
        BufferedReader reader = new BufferedReader(new FileReader(regfile));
        RegistryLocation = reader.readLine();
        

    }

    public ListModel getDownloadListModel() {
        return new ListModel() {

            public int getSize() {
                return transferManagerList.size();
            }

            public Object getElementAt(int index) {
                return transferManagerList.get(index);
            }

            public void addListDataListener(ListDataListener l) {
                downloadListListeners.add(l);
            }

            public void removeListDataListener(ListDataListener l) {
                downloadListListeners.remove(l);
            }
        };
    }
     public ListModel getUploadListModel() {
        return new ListModel() {

            public int getSize() {
                return transferManagerList.size();
            }

            public Object getElementAt(int index) {
                return transferManagerList.get(index);
            }

            public void addListDataListener(ListDataListener l) {
                uploadListListeners.add(l);
            }

            public void removeListDataListener(ListDataListener l) {
                uploadListListeners.remove(l);
            }
        };
    }

    public List<String> getErrors() {
        List<String> clone = new ArrayList<String>();
        synchronized (errors) {
            for (String s : errors) {
                clone.add(s);
            }
            errors.clear();
        }
        return clone;
    }

    private void fireDownloadAddedEvent(ListDataEvent e) {
        synchronized (progressMonitor) {
            progressMonitor.notify();
        }
        for (ListDataListener l : downloadListListeners) {
            l.intervalAdded(e);
        }
    }

    private void fireDownloadContentsChangedEvent(ListDataEvent e) {
        for (ListDataListener l : downloadListListeners) {
            l.contentsChanged(e);
        }
    }

       private void fireUploadAddedEvent(ListDataEvent e) {
        synchronized (progressMonitor) {
            progressMonitor.notify();
        }
        for (ListDataListener l : uploadListListeners) {
            l.intervalAdded(e);
        }
    }

    private void fireUploadContentsChangedEvent(ListDataEvent e) {
        for (ListDataListener l : uploadListListeners) {
            l.contentsChanged(e);
        }
    }

    public List<GuiDataObject> QueryRegistryForServers(String filename) {

        //TODO: add if file empty check
        //connect to registry



        GuiDataObject guiDataObject = null;
        List<GuiDataObject> possibleDownloads = new ArrayList<GuiDataObject>();

        Registry registry = null;
        QueryResponse qr = null;
        try {
            //passing in hardcoded registry ip address
            registry = ConnectToRegistry();
            picRegistry = (PicRegistry) registry.lookup("PicRegistry");
            qr = picRegistry.fileQuery(filename);
        } catch (RemoteException ex) {
            errors.add(ex.getMessage());
        } catch (NotBoundException nbe) {
            errors.add(nbe.getMessage());
        }
        if (qr != null) {
            FileInfo fileInfo = qr.getFileInfo();
            for (ServerInfo server : qr.getServers()) {
                possibleDownloads.add(new GuiDataObject(fileInfo, server));
            }
        }
        return possibleDownloads;
    }

    // Get a list of servers that are available for upload
    public List<ServerInfo> RetreiveAvailableServerListFromRegistry() {

        List<ServerInfo> list = new ArrayList<ServerInfo>();


        Registry registry = null;
        try {
            //passing in hardcoded registry ip address
            registry = ConnectToRegistry();
            picRegistry = (PicRegistry) registry.lookup("PicRegistry");
            list = picRegistry.getAvailableServerList();

            System.out.println("printing list size in method 'RetreiveAvailableServerListFromRegistry' " + list.size());


        } catch (RemoteException re) {

            System.out.println("Remot exception tryng to connect to registry" + re.getMessage() + re.getStackTrace());
        } catch (NotBoundException nbe) {

            System.out.println("Not Bound Exception" + nbe.getStackTrace());
        }

        //Todo: Assuming the list will not be null or empty

        //TODO: In case of null or empty, Registry could be down, Try another Registy?
        return list;


    }

    //checks to see if the server failed? File not accessible
    public boolean IsFaulty() {

        return true;
    }

    //return true after upload has successfully completed
    public void UploadFileToServer(File file, ServerInfo serverInfoObj) throws RemoteException {
        //needs to get server info here too to know which server was selected
        //uploading file to the server needs to fileInfoObject and filepath
        // ConnectClientToUpload(RequestType.CLIENTUPLOAD, fileInfoObject, filedata, server);
        //Add the file to the Current Transfers
        //currentTransfers.add( GuiDataObject??);

           Socket uploadSocket = null;
        try {
            uploadSocket = new Socket(serverInfoObj.getAddress(), serverInfoObj.getPort());
        } catch (IOException ioe) {
            errors.add(ioe.getMessage());
        }

            if (uploadSocket != null) {
            boolean abort = false;
            try {
                uploadSocket.setSoTimeout(4000);
            } catch (SocketException se) {
                errors.add("Server no longer available: " + uploadSocket.getInetAddress().getHostName());
                abort = true;
            }
            if (!abort) {
                ServerRequest request = new ServerRequest();
                //request.setFileInfo(fileInfoObj);
                FileInfo info=null;
                try {
                    info = new FileInfo(file);
                } catch (NoSuchAlgorithmException ex) {
                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                } catch (FileNotFoundException ex) {
                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                }
                info.setFileName(file.getName());
                request.setFileInfo(info);
                request.setFileName(file.getName());
                request.setType(RequestType.CLIENTUPLOAD);
                writeRequest(uploadSocket, request);
                Thread uploadThread = null;
                FileUploadManager mgr = null;
                try {

                    InputStream is = new FileInputStream(file);
                    mgr = new FileUploadManager(info, is, uploadSocket.getOutputStream(), 0);

                } catch (IOException ex) {
                    errors.add(ex.getMessage());
                }
                if (mgr != null) {
                    synchronized (transferManagerList) {
                        transferManagerList.add(mgr);
                    }
                    mgr.addTransferListener(this);
                    fireUploadAddedEvent(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, 0, transferManagerList.size() - 1));
                    mgr.start();
                    /*
                    synchronized (progressMonitor) {
                    progressMonitor.notify();
                    }
                     */
                }

            }
        }


    }
    /*
    public List<FileDownloadManager> getDownloadManagerList()
    {
    synchronized(downloadManagerList)
    {
    return downloadManagerList;
    }

    }
     */
    //return true after download has successfully completed

    public void writeRequest(Socket socket, ServerRequest request) {
        if (socket != null) {
            OutputStream serverStream = null;
            try {
                serverStream = socket.getOutputStream();
            } catch (IOException ex) {
                errors.add(ex.getMessage());
            }
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(serverStream);
            } catch (IOException ex) {
                errors.add(ex.getMessage());
            }
            if (oos != null) {
                try {
                    oos.writeObject(request);
                } catch (IOException ex) {
                    errors.add(ex.getMessage());
                }
            }
        } else {
            errors.add("Server is no longer available");
        }


    }

    public void DownloadFileFromServer(GuiDataObject file_server_info) {

        GuiDataObject downloadChoice = file_server_info;
        FileInfo fileInfoObj = downloadChoice.getMyFile();
        ServerInfo serverInfoObj = downloadChoice.getMyServer();
        String serverStatus = downloadChoice.getStatus();
        Socket downloadSocket = null;
        try {
            downloadSocket = new Socket(serverInfoObj.getAddress(), serverInfoObj.getPort());
        } catch (IOException ioe) {
            errors.add(ioe.getMessage());
        }
        if (downloadSocket != null) {
            boolean abort = false;
            try {
                downloadSocket.setSoTimeout(4000);
            } catch (SocketException se) {
                errors.add("Server no longer available: " + downloadSocket.getInetAddress().getHostName());
                abort = true;
            }
            if (!abort) {
                ServerRequest request = new ServerRequest();
                request.setFileInfo(fileInfoObj);
                request.setType(RequestType.CLIENTDOWNLOAD);
                writeRequest(downloadSocket, request);
                Thread downloadThread = null;
                FileDownloadManager mgr = null;
                try {
                    mgr = new FileDownloadManager(fileInfoObj, downloadSocket.getInputStream());

                } catch (IOException ex) {
                    errors.add(ex.getMessage());
                }
                if (mgr != null) {
                    synchronized (transferManagerList) {
                        transferManagerList.add(mgr);
                    }
                    mgr.addTransferListener(this);
                    fireDownloadAddedEvent(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, 0, transferManagerList.size() - 1));
                    mgr.start();
                    /*
                    synchronized (progressMonitor) {
                    progressMonitor.notify();
                    }
                     */
                }

            }
        }

    }

    public void writeBytesToFile(byte[] bytes, FileInfo fileInfoObj) {

        //creat a new empty file if it doesnt exist
        boolean blnCreated = false;

        try {
            System.out.println("----" + imageFilePath + "/" + fileInfoObj.getFileName());

            File fileOnClient = new File(imageFilePath + "/" + fileInfoObj.getFileName());
            blnCreated = fileOnClient.createNewFile();
            FileOutputStream fos = new FileOutputStream(fileOnClient);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            bos.write(bytes);
            bos.flush();
            bos.close();

            setImagePath("file:"+imageFilePath+ "/"+ fileInfoObj.getFileName());
            
           
        } catch (IOException ioe) {

            System.out.println("Error while creating a new empty file :" + ioe);

        }
    }

    public void actionPerformed(ActionEvent ae) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    /**
     *
     * Testing if file can be converted into bytes array and then read and stored on the client
     */
//
//   public static void main(String [] args){
//
//      System.out.println("hello");
//      Client client = new Client();
//      File f = new File("/Users/jasbh292/Desktop/igotyourback.jpg");
//
//      System.out.println("==---=" + f.getName());
//
//        try {
//
//
//            FileInfo ftest = new FileInfo(f);
//            ftest.setFileName("jasmine80.jpg");
//
//            System.out.println("===" + ftest.getFileName());
//            FileOutputStream fos = null;
//
//            String fileDownloadDirPath = "/Users/jasbh292/Desktop/testing";
//            client.writeBytesToFile(fos, null, fileDownloadDirPath, ftest);
//
//
//        } catch (NoSuchAlgorithmException ex) {
//            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (FileNotFoundException ex) {
//            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
//        }
//
//
//      //  writeBytesToFile(null, null, fileDownloadedDirectoryPath, null);
//    }
}
